import 'dart:async';
import 'dart:convert';

import 'package:http_parser/http_parser.dart';
import 'package:http_parser/src/chunked_coding/charcodes.dart';

import '../common/test_page.dart';

class ChunkedCodingTestPage extends TestPage{
  ChunkedCodingTestPage(super.title) {
    group('encoder', () {
      test('chunkedCoding.encode([1, 2, 3])', () {
        expect(chunkedCoding.encode([1, 2, 3]), ([$3, $cr, $lf, 1, 2, 3, $cr, $lf, $0, $cr, $lf, $cr, $lf]));
      });

      test('chunkedCoding.encode(Iterable<int>.generate(0xA7).toList())', () {
        final data = Iterable<int>.generate(0xA7).toList();
        expect(
            chunkedCoding.encode(data), (
                [$a, $7, $cr, $lf, ...data, $cr, $lf, $0, $cr, $lf, $cr, $lf]));
      });

      test('chunkedCoding.encode([])', () {
        expect(chunkedCoding.encode([]), ([$0, $cr, $lf, $cr, $lf]));
      });

      group('with chunked conversion', () {
        // late List<List<int>> results;
        // late ByteConversionSink sink;
        // setUp(() {
        //   results = [];
        //   final controller = StreamController<List<int>>(sync: true);
        //   controller.stream.listen(results.add);
        //   sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
        // });

        test('chunkedCoding.encoder.startChunkedConversion(controller.sink).add([1, 2, 3, 4]), .add([5, 6, 7]), .close()', () {
          List<List<int>> results = [];
          final controller = StreamController<List<int>>(sync: true);
          controller.stream.listen(results.add);
          ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
          sink.add([1, 2, 3, 4]);
          expect(
              results,
              ([
                [$4, $cr, $lf, 1, 2, 3, 4, $cr, $lf]
              ]));

          sink.add([5, 6, 7]);
          expect(
              results,
              ([
                [$4, $cr, $lf, 1, 2, 3, 4, $cr, $lf],
                [$3, $cr, $lf, 5, 6, 7, $cr, $lf],
              ]));

          sink.close();
          expect(
              results,
              ([
                [$4, $cr, $lf, 1, 2, 3, 4, $cr, $lf],
                [$3, $cr, $lf, 5, 6, 7, $cr, $lf],
                [$0, $cr, $lf, $cr, $lf],
              ]));
        });

        test('chunkedCoding.encoder.startChunkedConversion(controller.sink).add([]), .add([1, 2, 3], .add([]), .close())', () {
          List<List<int>> results = [];
          final controller = StreamController<List<int>>(sync: true);
          controller.stream.listen(results.add);
          ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
          sink.add([]);
          expect(results, ([<int>[]]));

          sink.add([1, 2, 3]);
          expect(
              results,
              ([
                <int>[],
                [$3, $cr, $lf, 1, 2, 3, $cr, $lf]
              ]));

          sink.add([]);
          expect(
              results,
              ([
                <int>[],
                [$3, $cr, $lf, 1, 2, 3, $cr, $lf],
                <int>[]
              ]));

          sink.close();
          expect(
              results,
              ([
                <int>[],
                [$3, $cr, $lf, 1, 2, 3, $cr, $lf],
                <int>[],
                [$0, $cr, $lf, $cr, $lf],
              ]));
        });

        group('addSlice()', () {
          test('chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], 1, 4, false)', () {
            List<List<int>> results = [];
            final controller = StreamController<List<int>>(sync: true);
            controller.stream.listen(results.add);
            ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
            sink.addSlice([1, 2, 3, 4, 5], 1, 4, false);
            expect(
                results,
                ([
                  [$3, $cr, $lf, 2, 3, 4, $cr, $lf]
                ]));
          });

          test("chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], 1, 1, false)", () {
            List<List<int>> results = [];
            final controller = StreamController<List<int>>(sync: true);
            controller.stream.listen(results.add);
            ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
            sink.addSlice([1, 2, 3, 4, 5], 1, 1, false);
            expect(results, ([<int>[]]));
          });

          test('chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], 1, 4, true)', () {
            List<List<int>> results = [];
            final controller = StreamController<List<int>>(sync: true);
            controller.stream.listen(results.add);
            ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
            sink.addSlice([1, 2, 3, 4, 5], 1, 4, true);
            expect(
                results,
                ([
                  [$3, $cr, $lf, 2, 3, 4, $cr, $lf, $0, $cr, $lf, $cr, $lf]
                ]));

            // Setting isLast shuld close the sink.
            expect(() => sink.add([]), '');
          });

          group('disallows', () {
            test('chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], -1, 4, false)', () {
              List<List<int>> results = [];
              final controller = StreamController<List<int>>(sync: true);
              controller.stream.listen(results.add);
              ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
              expect(() => sink.addSlice([1, 2, 3, 4, 5], -1, 4, false),
                  '');
            });

            test('chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], 3, 2, false)', () {
              List<List<int>> results = [];
              final controller = StreamController<List<int>>(sync: true);
              controller.stream.listen(results.add);
              ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
              expect(() => sink.addSlice([1, 2, 3, 4, 5], 3, 2, false),
                  '');
            });

            test('chunkedCoding.encoder.startChunkedConversion(controller.sink).addSlice([1, 2, 3, 4, 5], 1, 10, false)', () {
              List<List<int>> results = [];
              final controller = StreamController<List<int>>(sync: true);
              controller.stream.listen(results.add);
              ByteConversionSink sink = chunkedCoding.encoder.startChunkedConversion(controller.sink);
              expect(() => sink.addSlice([1, 2, 3, 4, 5], 1, 10, false),
                  '');
            });
          });
        });
      });
    });

    group('decoder', () {
      test('chunkedCoding.decode()', () {
        expect(
            chunkedCoding.decode([
              $3,
              $cr,
              $lf,
              1,
              2,
              3,
              $cr,
              $lf,
              $4,
              $cr,
              $lf,
              4,
              5,
              6,
              7,
              $cr,
              $lf,
              $0,
              $cr,
              $lf,
              $cr,
              $lf,
            ]),
            ([1, 2, 3, 4, 5, 6, 7]));
      });

      test('chunkedCoding.decode([a, 7, cr, lf, ...data, cr, lf, 0, cr, lf, cr, lf])', () {
        final data = Iterable<int>.generate(0xA7).toList();
        expect(
            chunkedCoding.decode(
                [$a, $7, $cr, $lf, ...data, $cr, $lf, $0, $cr, $lf, $cr, $lf]),
            (data));
      });

      test('chunkedCoding.decode([A, 7, cr, lf, ...data, cr, lf, 0, cr, lf, cr, lf])', () {
        final data = Iterable<int>.generate(0xA7).toList();
        expect(
            chunkedCoding.decode(
                [$A, $7, $cr, $lf, ...data, $cr, $lf, $0, $cr, $lf, $cr, $lf]),
            (data));
      });

      test('chunkedCoding.decode([0, cr, lf, cr, lf])', () {
        expect(chunkedCoding.decode([$0, $cr, $lf, $cr, $lf]), 'isEmpty');
      });

      group('disallows a message', () {
        test('chunkedCoding.decode([])', () {
          expect(() => chunkedCoding.decode([]), '');
        });

        test('chunkedCoding.decode([a])', () {
          expect(() => chunkedCoding.decode([$a]), '');
        });

        test('chunkedCoding.decode([a, cr])', () {
          expect(() => chunkedCoding.decode([$a, $cr]), '');
        });

        test('chunkedCoding.decode([a, cr, lf])', () {
          expect(
                  () => chunkedCoding.decode([$a, $cr, $lf]), '');
        });

        test('chunkedCoding.decode([a, cr, lf, 1, 2, 3])', () {
          expect(() => chunkedCoding.decode([$a, $cr, $lf, 1, 2, 3]),
              '');
        });

        test("chunkedCoding.decode([1, cr, lf, 1])", () {
          expect(() => chunkedCoding.decode([$1, $cr, $lf, 1]),
              '');
        });

        test("chunkedCoding.decode([1, cr, lf, 1, cr])", () {
          expect(() => chunkedCoding.decode([$1, $cr, $lf, 1, $cr]),
              '');
        });

        test("chunkedCoding.decode([1, cr, lf, 1, cr, lf])", () {
          expect(() => chunkedCoding.decode([$1, $cr, $lf, 1, $cr, $lf]),
              '');
        });

        test('chunkedCoding.decode([0, cr, lf])', () {
          expect(
                  () => chunkedCoding.decode([$0, $cr, $lf]), '');
        });

        test('chunkedCoding.decode([0, cr, lf, cr])', () {
          expect(() => chunkedCoding.decode([$0, $cr, $lf, $cr]),
              '');
        });

        test('chunkedCoding.decode([cr, lf, 0, cr, lf, cr, lf])', () {
          expect(() => chunkedCoding.decode([$cr, $lf, $0, $cr, $lf, $cr, $lf]),
              '');
        });

        test('chunkedCoding.decode([q, cr, lf, 0, cr, lf, cr, lf])', () {
          expect(
                  () => chunkedCoding.decode([$q, $cr, $lf, $0, $cr, $lf, $cr, $lf]),
              '');
        });
      });
    });
  }

}